#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <string.h>
#include <ctype.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sstream>
#include <map>
#include <iostream>
#include "rs274ngc.hh"
#include "rs274ngc_return.hh"
#include "interp_internal.hh"
#include "rs274ngc_interp.hh"
#include "inifile.hh"

// for HAL pin variables
#include "hal/hal.h"
#include "hal/hal_priv.h"

#include "rtapi/rtapi.h"

enum predefined_named_parameters
{
    NP_LINE,
    NP_MOTION_MODE,
    NP_PLANE,
    NP_CCOMP,
    NP_METRIC,
    NP_IMPERIAL,
    NP_ABSOLUTE,
    NP_INCREMENTAL,
    NP_INVERSE_TIME,
    NP_UNITS_PER_MINUTE,
    NP_UNITS_PER_REV,
    NP_COORD_SYSTEM,
    NP_TOOL_OFFSET,
    NP_RETRACT_R_PLANE,
    NP_RETRACT_OLD_Z,
    NP_SPINDLE_RPM_MODE,
    NP_SPINDLE_CSS_MODE,
    NP_IJK_ABSOLUTE_MODE,
    NP_LATHE_DIAMETER_MODE,
    NP_LATHE_RADIUS_MODE,
    NP_SPINDLE_ON,
    NP_SPINDLE_CW,
    NP_MIST,
    NP_FLOOD,
    NP_SPEED_OVERRIDE,
    NP_FEED_OVERRIDE,
    NP_FEED,
    NP_RPM,
    NP_CURRENT_TOOL,
    NP_SELECTED_POCKET,
    NP_CURRENT_POCKET,
    NP_X,
    NP_Y,
    NP_Z,
    NP_A,
    NP_B,
    NP_C,
    NP_U,
    NP_V,
    NP_W,
    NP_VALUE,
    NP_CALL_LEVEL,
    NP_REMAP_LEVEL,
    NP_SELECTED_TOOL,
    NP_VALUE_RETURNED,
    NP_TASK,
};

/****************************************************************************/

/*! read_named_parameter

Returned Value: int
   If read_integer_value returns an error code, this returns that code.
   If any of the following errors occur, this returns the error code shown.
   Otherwise, this returns INTERP_OK.
   1. The first character read is not a <:
      NCE_BUG_FUNCTION_SHOULD_NOT_HAVE_BEEN_CALLED
   2. The named parameter string is not terminated by >:
      NCE_NAMED_PARAMETER_NOT_TERSINATED
   3. The named parameter has not been defined before use:
      NCE_NAMED_PARAMETER_NOT_DEFINED

Side effects:
   The value of the given parameter is put into value points at.
   The counter is reset to point to the first character after the
   characters which make up the value.

Called by:  read_parameter

This attempts to read the value of a parameter out of the line,
starting at the index given by the counter.

According to the RS274/NGC manual [NCMS, p. 62], the characters following
# may be any "parameter expression". Thus, the following are legal
and mean the same thing (the value of the parameter whose number is
stored in parameter 2):
  ##2
  #[#2]


ADDED by K. Lerman
Named parameters are now supported.
#<_abcd> is a parameter with name "abcd" of global scope
#<abce> is a named parameter of local scope.

*/

int Interp::read_named_parameter(char* line,             //!< string: line of RS274/NGC code being processed
                                 int* counter,           //!< pointer to a counter for position on the line
                                 char* paramNameBuf,     //!< the name or parameter to be read
                                 jaka_value* value_ptr,  //!< pointer to double to be read
                                 double* parameters,     //!< array of system parameters
                                 bool check_exists)      //!< test for existence, not value
{
    static char name[] = "read_named_parameter";
    int exists;
    jaka_value value;
    parameter_map_iterator pi;

    CHP(find_named_param(paramNameBuf, &exists, &value));
    if (check_exists)
    {
        jaka_value testVal1, testVal2;
        testVal1.scalarValue = 1.0;
        *value_ptr = exists ? testVal1 : testVal2;
        return INTERP_OK;
    }
    if (exists)
    {
        *value_ptr = value;
        return INTERP_OK;
    }
    else
    {
        // do not require named parameters to be defined during a subroutine definition:
        if (_setup.defining_sub)
            return INTERP_OK;

        logNP("%s: referencing undefined named parameter '%s' level=%d", name, paramNameBuf, (paramNameBuf[0] == '_') ? 0 : _setup.call_level);
        ERS(INTERP_ERR_PARAMETER_NOT_DEFINED, _("Named parameter %s not defined"), paramNameBuf);
    }
    return INTERP_OK;
}

// if the variable is of the form '_ini[section]name', then treat it as
// an inifile  variable. Lookup section/name and cache the value
// as global and read-only.
// the shortest possible ini variable is '_ini[s]n' or 8 chars long .
int Interp::fetch_ini_param(const char* nameBuf, int* status, jaka_value* value)
{
    char* s;
    *status = 0;
    int n = strlen(nameBuf);

    if ((n > 7) && ((s = (char*)strchr(&nameBuf[6], ']')) != NULL))
    {
        IniFile inifile;
        int retval;
        int closeBracket = s - nameBuf;

        const char* iniFileName = ZUC_USER_CONFIG_FILE("usersettings.ini");
        if (!inifile.Open(iniFileName))
        {
            *status = 0;
            ERS(INTERP_ERR_OPEN_FILE_FAILED, _("cant open ini file '%s'"), iniFileName);
        }

        char capName[LINELEN];

        strncpy(capName, nameBuf, n);
        capName[n] = '\0';
        for (char* p = capName; *p != 0; p++) *p = toupper(*p);
        capName[closeBracket] = '\0';

        value->valueType = SCALAR_VALUE;
        if ((retval = inifile.Find(&(value->scalarValue), &capName[closeBracket + 1], &capName[5])) == 0)
        {
            *status = 1;
            inifile.Close();
        }
        else
        {
            inifile.Close();
            *status = 0;
            ERS(INTERP_ERR_KEY_NOT_FOUND_IN_INI, _("Named ini parameter #<%s> not found in inifile '%s': error=0x%x"), nameBuf, iniFileName, retval);
        }
    }
    return INTERP_OK;
}

// if the variable is of the form '_hal[hal_name]', then treat it as
// a HAL pin, signal or param. Lookup value, convert to float, and export as global and read-only.
// do not cache.
// the shortest possible ini variable is '_hal[x]' or 7 chars long .
int Interp::fetch_hal_param(const char* nameBuf, int* status, jaka_value* value)
{
    static int comp_id;
    int retval;
    int type = 0;
    hal_data_u* ptr;
    char hal_name[LINELEN];

    *status = 0;
    if (!comp_id)
    {
        char hal_comp[LINELEN];
        sprintf(hal_comp, "interp%d", getpid());
        comp_id = hal_init(hal_comp);  // manpage says: NULL ok - which fails miserably
        CHKS(comp_id < 0, INTERP_ERR_SYNTAX_FAILED, _("fetch_hal_param: hal_init(%s): %d"), hal_comp, comp_id);
        CHKS((retval = hal_ready(comp_id)), INTERP_ERR_SYNTAX_FAILED, _("fetch_hal_param: hal_ready(): %d"), retval);
    }
    char* s;
    int n = strlen(nameBuf);
    if ((n > 6) && ((s = (char*)strchr(&nameBuf[5], ']')) != NULL))
    {
        int closeBracket = s - nameBuf;
        hal_pin_t* pin;
        hal_sig_t* sig;
        hal_param_t* param;

        strncpy(hal_name, &nameBuf[5], closeBracket);
        hal_name[closeBracket - 5] = '\0';
        if (nameBuf[closeBracket + 1])
        {
            logOword("%s: trailing garbage after closing bracket", hal_name);
            *status = 0;
            ERS(INTERP_ERR_INTERNAL, "%s: trailing garbage after closing bracket", nameBuf);
        }
        // the result of these lookups could be cached in the parameter struct, but I'm not sure
        // this is a good idea - a removed pin/signal will not be noticed

        // I dont think that's needed - no change in pins/sigs/params
        // rtapi_mutex_get(&(hal_data->mutex));
        // rtapi_mutex_give(&(hal_data->mutex));

        if ((pin = halpr_find_pin_by_name(hal_name)) != NULL)
        {
            if (pin && !pin->signal)
            {
                logOword("%s: no signal connected", hal_name);
            }
            type = pin->type;
            if (pin->signal != 0)
            {
                sig = (hal_sig_t*)SHMPTR(pin->signal);
                ptr = (hal_data_u*)SHMPTR(sig->data_ptr);
            }
            else
            {
                ptr = (hal_data_u*)&(pin->dummysig);
            }
            goto assign;
        }
        if ((sig = halpr_find_sig_by_name(hal_name)) != NULL)
        {
            if (!sig->writers)
                logOword("%s: signal has no writer", hal_name);
            type = sig->type;
            ptr = (hal_data_u*)SHMPTR(sig->data_ptr);
            goto assign;
        }
        if ((param = halpr_find_param_by_name(hal_name)) != NULL)
        {
            type = param->type;
            ptr = (hal_data_u*)SHMPTR(param->data_ptr);
            goto assign;
        }
        *status = 0;
        ERS(INTERP_ERR_KEY_NOT_FOUND_IN_INI, "Named hal parameter #<%s> not found", nameBuf);
    }
    return INTERP_OK;

assign:
    switch (type)
    {
    case HAL_BIT:
        value->valueType = SCALAR_VALUE;
        value->scalarValue = (double)(ptr->b);
        break;
    case HAL_U32:
        value->valueType = SCALAR_VALUE;
        value->scalarValue = (double)(ptr->u);
        break;
    case HAL_S32:
        value->valueType = SCALAR_VALUE;
        value->scalarValue = (double)(ptr->s);
        break;
    case HAL_FLOAT:
        value->valueType = SCALAR_VALUE;
        value->scalarValue = (double)(ptr->f);
        break;
    }
    logOword("%s: value=%f", hal_name, value->scalarValue);
    *status = 1;
    return INTERP_OK;
}

int Interp::find_named_param(const char* nameBuf,  //!< pointer to name to be read
                             int* status,          //!< pointer to return status 1 => found
                             jaka_value* value     //!< pointer to value of found parameter
)
{
    // rtapi_print("find_named_param called, param name %s!\n", nameBuf);

    context_pointer frame;
    parameter_map_iterator pi;
    int level;

    level = (nameBuf[0] == '_') ? 0 : _setup.call_level;  // determine scope
    frame = &_setup.sub_context[level];
    *status = 0;

    pi = frame->named_params.find(nameBuf);
    if (pi == frame->named_params.end())
    {  // not found
        int exists = 0;
        jaka_value initValue;
        if (FEATURE(INI_VARS) && (strncasecmp(nameBuf, "_ini[", 5) == 0))
        {
            fetch_ini_param(nameBuf, &exists, &initValue);
            if (exists)
            {
                logNP("parameter '%s' retrieved from INI: %f", nameBuf, initValue.scalarValue);
                *value = initValue;
                *status = 1;
                parameter_value param;  // cache the value
                param.value = initValue;
                param.attr = PA_GLOBAL | PA_READONLY | PA_FROM_INI;
                _setup.sub_context[0].named_params[strstore(nameBuf)] = param;
                return INTERP_OK;
            }
        }
        if (FEATURE(HAL_PIN_VARS) && (strncasecmp(nameBuf, "_hal[", 5) == 0))
        {
            fetch_hal_param(nameBuf, &exists, &initValue);
            if (exists)
            {
                logNP("parameter '%s' retrieved from HAL: %f", nameBuf, initValue.scalarValue);
                *value = initValue;
                *status = 1;
                return INTERP_OK;
            }
        }

        //   rtapi_print("named param %s was not found!\n", nameBuf);
        jaka_value defValue;
        *value = defValue;
        *status = 0;
    }
    else
    {
        parameter_pointer pv = &pi->second;
        if (pv->attr & PA_UNSET)
            logNP("warning: referencing unset variable '%s'", nameBuf);
        if (pv->attr & PA_USE_LOOKUP)
        {
            CHP(lookup_named_param(nameBuf, pv->value.scalarValue, value));
            *status = 1;
        }

        // returning a string sets the interpreter error message and aborts
        // ok, that callable returned something botched.
        else
        {
            *value = pv->value;
            *status = 1;
        }
    }
    return INTERP_OK;
}

void Interp::store_variables(const std::string& varName, const jaka_value& value)
{
    if (varName.find("_", 0) == 0)
    {
        switch (value.valueType)
        {
        case 0:
            _redis.storeVariable(varName, value.scalarValue);
            break;
        case 1:
            _redis.storeVariable(varName, value.arrValue);
            break;
        case 2:
            _redis.storeVariable(varName, value.strValue);
            break;
        default:
            break;
        }
    }
}

int Interp::store_named_param(setup_pointer settings,
                              const char* nameBuf,   //!< pointer to name to be written
                              jaka_value value,      //!< value to be written
                              int override_readonly  //!< set to true to init a r/o parameter
)
{
    context_pointer frame;
    int level;
    parameter_map_iterator pi;

    level = (nameBuf[0] == '_') ? 0 : _setup.call_level;  // determine scope
    frame = &settings->sub_context[level];

    pi = frame->named_params.find(nameBuf);
    if (pi == frame->named_params.end())
    {
        ERS(INTERP_ERR_INTERNAL, _("Internal error: Could not assign #<%s>"), nameBuf);
    }
    else
    {
        parameter_pointer pv = &pi->second;

        CHKS(((pv->attr & PA_GLOBAL) && level), INTERP_ERR_SYNTAX_FAILED, "BUG: variable '%s' marked global, but assigned at level %d", nameBuf, level);

        if ((pv->attr & PA_READONLY) && !override_readonly)
        {
            ERS(INTERP_ERR_INTERNAL, _("Cannot assign to read-only parameter #<%s>"), nameBuf);
        }
        else
        {
            pv->value = value;
            // std::cout<<"get_thread_type:"<<get_thread_type()<<std::endl;
            if (get_thread_type() == 0)
            {
                store_variables(nameBuf, value);
            }

            pv->attr &= ~PA_UNSET;
            logNP("store_named_parameter: level[%d] %s value.scalarValue=%lf", level, nameBuf, value.scalarValue);
        }
    }
    return INTERP_OK;
}

int Interp::add_named_param(const char* nameBuf,  //!< pointer to name to be added
                            int attr)             //!< see PA_* defs in interp_internal.hh
{
    static char name[] = "add_named_param";
    int findStatus;
    jaka_value value;
    int level;
    parameter_value param;

    // look it up to see if already exists
    CHP(find_named_param(nameBuf, &findStatus, &value));

    if (findStatus)
    {
        logNP("%s: parameter:|%s| already exists", name, nameBuf);
        return INTERP_OK;
    }
    attr |= PA_UNSET;

    if (nameBuf[0] != '_')
    {  // local scope
        level = _setup.call_level;
    }
    else
    {
        level = 0;  // call level zero is global scope
        attr |= PA_GLOBAL;
    }

    jaka_value initValue;
    param.attr = attr;
    param.value = initValue;

    _setup.sub_context[level].named_params[strstore(nameBuf)] = param;
    return INTERP_OK;
}

int Interp::free_named_parameters(context_pointer frame)
{
    frame->named_params.clear();
    return INTERP_OK;
}

// just a shorthand
int Interp::init_readonly_param(const char* nameBuf,  //!< pointer to name to be added
                                jaka_value value,     //!< initial value
                                int attr)             //!< see PA_* defs in interp_internal.hh
{
    // static char name[] = "init_readonly_param";
    CHKS(add_named_param((char*)nameBuf, PA_READONLY | attr), INTERP_ERR_SYNTAX_FAILED, "adding r/o '%s'", nameBuf);
    CHKS(store_named_param(&_setup, (char*)nameBuf, value, OVERRIDE_READONLY), INTERP_ERR_SYNTAX_FAILED, "storing r/o '%s' %f", nameBuf, value.scalarValue);
    return INTERP_OK;
}

int Interp::lookup_named_param(const char* nameBuf, double index, jaka_value* value)
{
    int cmd = round_to_int(index);

    switch (cmd)
    {
        // some active_g_codes fields

    case NP_LINE:  // _line - sequence number
        value->valueType = SCALAR_VALUE;
        value->scalarValue = _setup.sequence_number;
        break;

    case NP_MOTION_MODE:  // _motion_mode
        value->valueType = SCALAR_VALUE;
        value->scalarValue = _setup.motion_mode;
        break;

    case NP_PLANE:  // _plane
        switch (_setup.plane)
        {
        case CANON_PLANE_XY:
            value->valueType = SCALAR_VALUE;
            value->scalarValue = G_17;
            break;
        case CANON_PLANE_XZ:
            value->valueType = SCALAR_VALUE;
            value->scalarValue = G_18;
            break;
        case CANON_PLANE_YZ:
            value->valueType = SCALAR_VALUE;
            value->scalarValue = G_19;
            break;
        case CANON_PLANE_UV:
            value->valueType = SCALAR_VALUE;
            value->scalarValue = G_17_1;
            break;
        case CANON_PLANE_UW:
            value->valueType = SCALAR_VALUE;
            value->scalarValue = G_18_1;
            break;
        case CANON_PLANE_VW:
            value->valueType = SCALAR_VALUE;
            value->scalarValue = G_19_1;
            break;
        }
        break;

    case NP_CCOMP:  // _ccomp - cutter compensation
        value->valueType = SCALAR_VALUE;
        value->scalarValue = (_setup.cutter_comp_side == RIGHT) ? G_42 : (_setup.cutter_comp_side == LEFT) ? G_41 : G_40;
        break;

    case NP_METRIC:  // _metric
        value->valueType = SCALAR_VALUE;
        value->scalarValue = (_setup.length_units == CANON_UNITS_MM);
        break;

    case NP_IMPERIAL:  // _imperial
        value->valueType = SCALAR_VALUE;
        value->scalarValue = (_setup.length_units == CANON_UNITS_INCHES);
        break;

    case NP_ABSOLUTE:  // _absolute - distance mode
        value->valueType = SCALAR_VALUE;
        value->scalarValue = (_setup.distance_mode == MODE_ABSOLUTE);
        break;

    case NP_INCREMENTAL:  // _incremental - distance mode
        value->valueType = SCALAR_VALUE;
        value->scalarValue = (_setup.distance_mode == MODE_INCREMENTAL);
        break;

    case NP_INVERSE_TIME:  // _inverse_time - feed mode
        value->valueType = SCALAR_VALUE;
        value->scalarValue = (_setup.feed_mode == INVERSE_TIME);
        break;

    case NP_UNITS_PER_MINUTE:  // _units_per_minute - feed mode
        value->valueType = SCALAR_VALUE;
        value->scalarValue = (_setup.feed_mode == UNITS_PER_MINUTE);
        break;

    case NP_UNITS_PER_REV:  // _units_per_rev - feed mode
        value->valueType = SCALAR_VALUE;
        value->scalarValue = (_setup.feed_mode == UNITS_PER_REVOLUTION);
        break;

    case NP_COORD_SYSTEM:  // _coord_system - 0-9
        value->valueType = SCALAR_VALUE;
        value->scalarValue = (_setup.origin_index < 7) ? (530 + (10 * _setup.origin_index)) : (584 + _setup.origin_index);
        break;

    case NP_TOOL_OFFSET:  // _tool_offset
        value->valueType = SCALAR_VALUE;
        value->scalarValue = (_setup.tool_offset.tran.x || _setup.tool_offset.tran.y || _setup.tool_offset.tran.z || _setup.tool_offset.a ||
                              _setup.tool_offset.b || _setup.tool_offset.c || _setup.tool_offset.u || _setup.tool_offset.v || _setup.tool_offset.w);
        break;

    case NP_RETRACT_R_PLANE:  // _retract_r_plane - G98
        value->valueType = SCALAR_VALUE;
        value->scalarValue = (_setup.retract_mode == R_PLANE);
        break;

    case NP_RETRACT_OLD_Z:  // _retract_old_z - G99
        value->valueType = SCALAR_VALUE;
        value->scalarValue = (_setup.retract_mode == OLD_Z);
        break;

    case NP_SPINDLE_RPM_MODE:  // _spindle_rpm_mode G97
        value->valueType = SCALAR_VALUE;
        value->scalarValue = (_setup.spindle_mode == CONSTANT_RPM);
        break;

    case NP_SPINDLE_CSS_MODE:  // _spindle_css_mode G96
        value->valueType = SCALAR_VALUE;
        value->scalarValue = (_setup.spindle_mode == CONSTANT_SURFACE);
        break;

    case NP_IJK_ABSOLUTE_MODE:  //_ijk_absolute_mode - G90.1
        value->valueType = SCALAR_VALUE;
        value->scalarValue = (_setup.ijk_distance_mode == MODE_ABSOLUTE);
        break;

    case NP_LATHE_DIAMETER_MODE:  // _lathe_diameter_mode - G7
        value->valueType = SCALAR_VALUE;
        value->scalarValue = _setup.lathe_diameter_mode;
        break;

    case NP_LATHE_RADIUS_MODE:  // _lathe_radius_mode - G8
        value->valueType = SCALAR_VALUE;
        value->scalarValue = (_setup.lathe_diameter_mode == 0);
        break;

        // some active_m_codes fields

    case NP_SPINDLE_ON:  // _spindle_on
        value->valueType = SCALAR_VALUE;
        value->scalarValue = (_setup.spindle_turning != CANON_STOPPED);
        break;

    case NP_SPINDLE_CW:  // spindle_cw
        value->valueType = SCALAR_VALUE;
        value->scalarValue = (_setup.spindle_turning == CANON_CLOCKWISE);
        break;

    case NP_MIST:  // mist
        value->valueType = SCALAR_VALUE;
        value->scalarValue = _setup.mist;
        break;

    case NP_FLOOD:  // flood
        value->valueType = SCALAR_VALUE;
        value->scalarValue = _setup.flood;
        break;

    case NP_FEED_OVERRIDE:  // feed override
        value->valueType = SCALAR_VALUE;
        value->scalarValue = _setup.feed_override;
        break;

    // from active_settings:
    case NP_FEED:  // feed
        value->valueType = SCALAR_VALUE;
        value->scalarValue = _setup.feed_rate;
        break;

    case NP_RPM:  // speed (rpm)
        value->valueType = SCALAR_VALUE;
        value->scalarValue = abs(_setup.speed);
        break;

    case NP_CURRENT_TOOL:
        value->valueType = SCALAR_VALUE;
        value->scalarValue = _setup.tool_table[_setup.current_pocket].toolno;
        break;

    case NP_SELECTED_POCKET:
        value->valueType = SCALAR_VALUE;
        value->scalarValue = _setup.selected_pocket;
        break;

    case NP_CURRENT_POCKET:
        value->valueType = SCALAR_VALUE;
        value->scalarValue = _setup.current_pocket;
        break;

    case NP_SELECTED_TOOL:
        value->valueType = SCALAR_VALUE;
        value->scalarValue = _setup.selected_tool;
        break;

    case NP_X:  // current position
        value->valueType = SCALAR_VALUE;
        value->scalarValue = _setup.current_x;
        break;

    case NP_Y:  // current position
        value->valueType = SCALAR_VALUE;
        value->scalarValue = _setup.current_y;
        break;

    case NP_Z:  // current position
        value->valueType = SCALAR_VALUE;
        value->scalarValue = _setup.current_z;
        break;

    case NP_A:  // current position
        value->valueType = SCALAR_VALUE;
        value->scalarValue = _setup.AA_current;
        break;

    case NP_B:  // current position
        value->valueType = SCALAR_VALUE;
        value->scalarValue = _setup.BB_current;
        break;

    case NP_C:  // current position
        value->valueType = SCALAR_VALUE;
        value->scalarValue = _setup.CC_current;
        break;

    case NP_U:  // current position
        value->valueType = SCALAR_VALUE;
        value->scalarValue = _setup.u_current;
        break;

    case NP_V:  // current position
        value->valueType = SCALAR_VALUE;
        value->scalarValue = _setup.v_current;
        break;

    case NP_W:  // current position
        value->valueType = SCALAR_VALUE;
        value->scalarValue = _setup.w_current;
        break;

    // o-word subs may optionally have an
    // expression after endsub and return
    // this 'function return value' is accessible as '_value'
    case NP_VALUE:
        value->valueType = SCALAR_VALUE;
        value->scalarValue = _setup.return_value;
        break;

    // predicate: the last NGC procedure did/did not return a value
    case NP_VALUE_RETURNED:
        value->valueType = SCALAR_VALUE;
        value->scalarValue = _setup.value_returned;
        break;

    case NP_CALL_LEVEL:
        value->valueType = SCALAR_VALUE;
        value->scalarValue = _setup.call_level;
        break;

    case NP_REMAP_LEVEL:
        value->valueType = SCALAR_VALUE;
        value->scalarValue = _setup.remap_level;
        break;

    case NP_TASK:
        extern int _task;  // zero in gcodemodule, 1 in zuctaskmod
        value->valueType = SCALAR_VALUE;
        value->scalarValue = _task;
        break;

    default:
        ERS(INTERP_ERR_UNHANDLED_INDEX, _("BUG: lookup_named_param(%s): unhandled index=%fn"), nameBuf, index);
    }
    return INTERP_OK;
}

int Interp::init_python_predef_parameter(const char* name)
{
    int exists = 0;
    jaka_value value, initValue;
    parameter_value param;

    if (name[0] == '_')
    {  // globals only
        find_named_param(name, &exists, &value);
        if (exists)
        {
            fprintf(stderr, "warning: redefining named parameter %s\n", name);
            _setup.sub_context[0].named_params.erase(name);
        }
        param.value = initValue;
        param.attr = PA_READONLY | PA_PYTHON | PA_GLOBAL;
        _setup.sub_context[0].named_params[strstore(name)] = param;
    }
    return INTERP_OK;
}

int Interp::init_named_parameters()
{
    // version       major   minor      Note
    // ------------ -------- ---------- -------------------------------------
    // M.N.m         M.N     0.m        normal format
    // M.N.m~xxx     M.N     0.m        pre-release format
    const char* pkgversion = ZUC_PACKAGE_VERSION;  //examples: 2.4.6, 2.5.0~pre
    const char* version_major = "0_vmajor";        // named_parameter name (use lower case)
    const char* version_minor = "0_vminor";        // named_parameter name (use lower case)
    double vmajor = 0.0, vminor = 0.0;
    sscanf(pkgversion, "%lf%lf", &vmajor, &vminor);

    jaka_value value;
    value.valueType = SCALAR_VALUE;

    value.scalarValue = vmajor;
    init_readonly_param(version_major, value, 0);
    value.scalarValue = vminor;
    init_readonly_param(version_minor, value, 0);

    // params tagged with PA_USE_LOOKUP will call the lookup_named_param()
    // method. The value is used as a index for the switch() statement.

    // the active_g_codes fields

    // I guess this is the line number
    value.scalarValue = NP_LINE;
    init_readonly_param("0_line", value, PA_USE_LOOKUP);

    // any of G1 G2 G3 G5.2 G73 G80 G82 G83 G86 G87 G88 G89
    // value is number after 'G' mutiplied by 10 (10,20,30,52..)
    value.scalarValue = NP_MOTION_MODE;
    init_readonly_param("0_motion_mode", value, PA_USE_LOOKUP);

    // G17/18/19/17.1/18.1/19.1 -> return 170/180/190/171/181/191
    value.scalarValue = NP_PLANE;
    init_readonly_param("0_plane", value, PA_USE_LOOKUP);

    // return 400,410,420 depending if (G40,G41,G42) is on
    value.scalarValue = NP_CCOMP;
    init_readonly_param("0_ccomp", value, PA_USE_LOOKUP);

    // 1.0 if G21 is on
    value.scalarValue = NP_METRIC;
    init_readonly_param("0_metric", value, PA_USE_LOOKUP);

    // 1.0 if G20 is on
    value.scalarValue = NP_IMPERIAL;
    init_readonly_param("0_imperial", value, PA_USE_LOOKUP);

    //1.0 if G90 is on
    value.scalarValue = NP_ABSOLUTE;
    init_readonly_param("0_absolute", value, PA_USE_LOOKUP);

    //1.0 if G91 is on
    value.scalarValue = NP_INCREMENTAL;
    init_readonly_param("0_incremental", value, PA_USE_LOOKUP);

    // 1.0 if G93 is on
    value.scalarValue = NP_INVERSE_TIME;
    init_readonly_param("0_inverse_time", value, PA_USE_LOOKUP);

    // 1.0 if G94 is on
    value.scalarValue = NP_UNITS_PER_MINUTE;
    init_readonly_param("0_units_per_minute", value, PA_USE_LOOKUP);

    // 1.0 if G95 is on
    value.scalarValue = NP_UNITS_PER_REV;
    init_readonly_param("0_units_per_rev", value, PA_USE_LOOKUP);

    // 0..9 for G54..G59.3
    value.scalarValue = NP_COORD_SYSTEM;
    init_readonly_param("0_coord_system", value, PA_USE_LOOKUP);

    // 1.0 if G43 is on
    value.scalarValue = NP_TOOL_OFFSET;
    init_readonly_param("0_tool_offset", value, PA_USE_LOOKUP);

    // 1 if G98 set
    value.scalarValue = NP_RETRACT_R_PLANE;
    init_readonly_param("0_retract_r_plane", value, PA_USE_LOOKUP);

    // 1 if G99 set
    value.scalarValue = NP_RETRACT_OLD_Z;
    init_readonly_param("0_retract_old_z", value, PA_USE_LOOKUP);

    // really esoteric
    // value.scalarValue = 110;
    // init_readonly_param("0_control_mode", value PA_USE_LOOKUP);

    // 1 if G97 is on
    value.scalarValue = NP_SPINDLE_RPM_MODE;
    init_readonly_param("0_spindle_rpm_mode", value, PA_USE_LOOKUP);

    value.scalarValue = NP_SPINDLE_CSS_MODE;
    init_readonly_param("0_spindle_css_mode", value, PA_USE_LOOKUP);

    // 1 if G90.1 is on
    value.scalarValue = NP_IJK_ABSOLUTE_MODE;
    init_readonly_param("0_ijk_absolute_mode", value, PA_USE_LOOKUP);

    // 1 if G7 is on
    value.scalarValue = NP_LATHE_DIAMETER_MODE;
    init_readonly_param("0_lathe_diameter_mode", value, PA_USE_LOOKUP);

    // 1 if G8 is on
    value.scalarValue = NP_LATHE_RADIUS_MODE;
    init_readonly_param("0_lathe_radius_mode", value, PA_USE_LOOKUP);

    // the active_m_codes fields
    value.scalarValue = NP_SPINDLE_ON;
    init_readonly_param("0_spindle_on", value, PA_USE_LOOKUP);
    value.scalarValue = NP_SPINDLE_CW;
    init_readonly_param("0_spindle_cw", value, PA_USE_LOOKUP);

    value.scalarValue = NP_SPEED_OVERRIDE;
    init_readonly_param("0_speed_override", value, PA_USE_LOOKUP);
    value.scalarValue = NP_FEED_OVERRIDE;
    init_readonly_param("0_feed_override", value, PA_USE_LOOKUP);

    // active_settings
    value.scalarValue = NP_FEED;
    init_readonly_param("0_feed", value, PA_USE_LOOKUP);
    value.scalarValue = NP_RPM;
    init_readonly_param("0_rpm", value, PA_USE_LOOKUP);

    // tool related
    value.scalarValue = NP_CURRENT_TOOL;
    init_readonly_param("0_current_tool", value, PA_USE_LOOKUP);
    value.scalarValue = NP_CURRENT_POCKET;
    init_readonly_param("0_current_pocket", value, PA_USE_LOOKUP);
    value.scalarValue = NP_SELECTED_POCKET;
    init_readonly_param("0_selected_pocket", value, PA_USE_LOOKUP);
    value.scalarValue = NP_SELECTED_TOOL;
    init_readonly_param("0_selected_tool", value, PA_USE_LOOKUP);

    // current position - alias to #5420-#5429
    value.scalarValue = NP_X;
    init_readonly_param("0_x", value, PA_USE_LOOKUP);
    value.scalarValue = NP_Y;
    init_readonly_param("0_y", value, PA_USE_LOOKUP);
    value.scalarValue = NP_Z;
    init_readonly_param("0_z", value, PA_USE_LOOKUP);
    value.scalarValue = NP_A;
    init_readonly_param("0_a", value, PA_USE_LOOKUP);
    value.scalarValue = NP_B;
    init_readonly_param("0_b", value, PA_USE_LOOKUP);
    value.scalarValue = NP_C;
    init_readonly_param("0_c", value, PA_USE_LOOKUP);
    value.scalarValue = NP_U;
    init_readonly_param("0_u", value, PA_USE_LOOKUP);
    value.scalarValue = NP_V;
    init_readonly_param("0_v", value, PA_USE_LOOKUP);
    value.scalarValue = NP_W;
    init_readonly_param("0_w", value, PA_USE_LOOKUP);

    // last (optional) endsub/return value
    value.scalarValue = NP_VALUE;
    init_readonly_param("0_value", value, PA_USE_LOOKUP);

    // predicate: last NGC procedure did return a value on endsub/return
    value.scalarValue = NP_VALUE_RETURNED;
    init_readonly_param("0_value_returned", value, PA_USE_LOOKUP);

    // predicate: 1 in zuctaskmod instance, 0 in UI - control preview behaviour
    value.scalarValue = NP_TASK;
    init_readonly_param("0_task", value, PA_USE_LOOKUP);

    // debugging aids
    value.scalarValue = NP_CALL_LEVEL;
    init_readonly_param("0_call_level", value, PA_USE_LOOKUP);
    value.scalarValue = NP_REMAP_LEVEL;
    init_readonly_param("0_remap_level", value, PA_USE_LOOKUP);

    return INTERP_OK;
}
